#include "file_handler.h"
#include "http.h"

#include <algorithm>

#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>


bool TStaticFileHandler::Init( const std::string& path
                             , unsigned int ioBufferSize )
{
    if (path.empty()) {
        return false;
    }

    int serveDir    =   open(path.c_str(), O_RDONLY);
    if (serveDir < 0) {
        return false;
    }

    struct  stat    fileStat;
    if (0 != fstat(serveDir, &fileStat)) {
        return false;
    }
    if (!S_ISDIR(fileStat.st_mode)) {
        return false;
    }
    BasePath        =   path;

    if (*BasePath.rbegin() == '/') {
        BasePath.erase(BasePath.size() - 1);
    }

    try {
        IoBuffer.assign(ioBufferSize, 0);
    }
    catch (std::bad_alloc except) {
        return false;
    }
    return true;
}

class TFileWatcher {
public:
    TFileWatcher(int handler)
    : Handler(handler)
    {}
    ~TFileWatcher();
private:
    int Handler;
};

TFileWatcher::~TFileWatcher()
{
    close(Handler);
}

void TStaticFileHandler::Handle(int connectionId)
{
    char*           readHeaderBuffer        =   &IoBuffer[0];
    unsigned int    readHeaderBufferSize    =   IoBuffer.size() / 2;
    char*           writeHeaderBuffer       =   &IoBuffer[0] + readHeaderBufferSize;
    unsigned int    writeHeaderBufferSize   =   readHeaderBufferSize;

    unsigned int    headerSize              =   0;
    if (!ReadHeader(readHeaderBuffer, readHeaderBufferSize, connectionId, headerSize)) {
        WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 400, 0, connectionId);
        return;
    }

    THttpRequest    request =   ParseHeader(readHeaderBuffer, headerSize);

    if (!request.IsValid()) {
        WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 400, 0, connectionId);
        return;
    }

    THttpRequest::ESupportedMethods method  =   request.GetMethod();

    if (THttpRequest::UNSUPPORTED == method) {
        WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 501, 0, connectionId);
        return;
    }

    std::string filePath    =   BasePath;
    filePath.append(request.Path, request.PathLen);
    int responseFile        =   open(filePath.c_str(), O_RDONLY);
    if (responseFile < 0) {
        WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 404, 0, connectionId);
        return;
    }
    TFileWatcher    autoFileCloser(responseFile);

    struct  stat    fileStat;
    off_t           fileSize    =   0;
    if (0 != fstat(responseFile, &fileStat)) {
        WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 404, 0, connectionId);
    }

    if (!S_ISREG(fileStat.st_mode)) {
        WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 404, 0, connectionId);
        return;
    }
    fileSize    =   fileStat.st_size;

    if (WriteHeader(writeHeaderBuffer, writeHeaderBufferSize, 200, fileSize, connectionId)) {
        if (THttpRequest::GET == method) {
            bool    isTransferSuccess   =   FileTransfer(connectionId, responseFile);
        }
    }
}

static int writeBytes(const char* buffer, unsigned int size, int connectionId)
{
    const char* bufferIter      =   buffer;
    const char* bufferEnd       =   bufferIter + size;
    int         bytesWritten    =   1;
    while ((0 < bytesWritten) && (bufferIter != bufferEnd)) {
        bytesWritten    =   write(connectionId, bufferIter, bufferEnd - bufferIter);
        bufferIter      +=  bytesWritten;
    }
    return bufferIter - buffer;
}

bool TStaticFileHandler::WriteHeader( char* buffer
                                    , unsigned int bufferSize
                                    , int status
                                    , unsigned int fileSize
                                    , int connectionId )
{
    const TStatusToMessage& mapper(GetHttpStatusMessages());

    if (status >= mapper.size()) {
        return false;
    }

    int bytesPrintfed   =   snprintf( buffer
                                    , bufferSize
                                    , THttpResponseTemplate::KStatusLine
                                    , status
                                    , mapper[status] );

    if (bufferSize <= bytesPrintfed) {
        return false;
    }

    int bytesWritten    =   writeBytes(buffer, bytesPrintfed, connectionId);

    if (bytesWritten != bytesPrintfed) {
        return false;
    }

    if (THttpResponseTemplate::KServerLen != writeBytes( THttpResponseTemplate::KServer
                                                       , THttpResponseTemplate::KServerLen
                                                       , connectionId )) {
        return false;
    }

    if (THttpResponseTemplate::KConnectionCloseLen != writeBytes( THttpResponseTemplate::KConnectionClose
                                                                , THttpResponseTemplate::KConnectionCloseLen
                                                                , connectionId )) {
        return false;
    }

    if (0 != fileSize) {
        bytesPrintfed   =   snprintf( buffer
                                    , bufferSize
                                    , THttpResponseTemplate::KContentLength
                                    , fileSize );

        if (bufferSize <= bytesPrintfed) {
            return false;
        }

        int bytesWritten    =   writeBytes(buffer, bytesPrintfed, connectionId);

        if (bytesWritten != bytesPrintfed) {
            return false;
        }

        if (THttpResponseTemplate::KContentTypeLen != writeBytes( THttpResponseTemplate::KContentType
                                                                , THttpResponseTemplate::KContentTypeLen
                                                                , connectionId )) {
            return false;
        }
    }

    if (2 != writeBytes("\r\n", 2, connectionId)) {
        return false;
    }

    return true;
}

bool TStaticFileHandler::ReadHeader( char* buffer
                                   , unsigned int size
                                   , int connectionId
                                   , unsigned int& headerSize )
{
    int     readBytes       =   1;
    char*   ioBufferPtr     =   buffer;
    char*   ioBufferPtrEnd  =   ioBufferPtr + size;
    while ( (0 < readBytes)
          &&
            (ioBufferPtr != ioBufferPtrEnd)
          &&
            ((ioBufferPtr - buffer < 4) || (0 != strncmp("\r\n\r\n", ioBufferPtr - 4, 4))) ) {
        readBytes   =   read(connectionId, ioBufferPtr, ioBufferPtrEnd - ioBufferPtr);
        ioBufferPtr +=  readBytes;
    }
    headerSize  =   ioBufferPtr - buffer;
    return ((headerSize > 4) && (0 == strncmp("\r\n\r\n", ioBufferPtr - 4, 4)));
}

bool TStaticFileHandler::FileTransfer(int connectionId,  int file)
{
    int     readBytes       =   1;
    int     bytesWritten    =   1;
    size_t  bufferSize      =   IoBuffer.size();

    while ((0 < readBytes) && (0 < bytesWritten)) {
        char*   ioBufferPtr     =   &IoBuffer[0];
        char*   ioBufferPtrEnd  =   ioBufferPtr + bufferSize;
        while ((0 < readBytes) && (ioBufferPtr != ioBufferPtrEnd)) {
            readBytes   =   read(file, ioBufferPtr, ioBufferPtrEnd - ioBufferPtr);
            ioBufferPtr +=  readBytes;
        }

        bytesWritten    =   writeBytes( &IoBuffer[0]
                                      , ioBufferPtr - &IoBuffer[0]
                                      , connectionId );
    }
    return ((0 == readBytes) && (0 == bytesWritten));
}

static bool notSP(char c)
{
    return c != ' ';
}

static bool findNotSPPart(const char* buffer, const char* bufferEnd, const char** place, unsigned int& len)
{
    const char* start       =   std::find_if(buffer, bufferEnd, notSP);
    if (start == bufferEnd) {
        return false;
    }
    const char* stop        =   std::find(start, bufferEnd, ' ');
    *place  =   start;
    len     =   stop - start;
    return true;
}

THttpRequest TStaticFileHandler::ParseHeader(const char* buffer, unsigned int size)
{
    THttpRequest        request;
    static const char   kStatusLineEnd[]    =   "\r\n";
    const char*         bufferEnd           =   buffer + size;
    const char*         statusLineEnd       =   std::search( buffer
                                                           , bufferEnd
                                                           , kStatusLineEnd
                                                           , kStatusLineEnd + 2);
    if (bufferEnd == statusLineEnd) {
        return request;
    }

    if (!findNotSPPart(buffer, statusLineEnd, &request.Method, request.MethodLen)) {
        return request;
    }
    buffer  =   request.Method + request.MethodLen;

    if (!findNotSPPart(buffer, statusLineEnd, &request.Path, request.PathLen)) {
        return request;
    }
    buffer  =   request.Path + request.PathLen;

    if (!findNotSPPart(buffer, statusLineEnd, &request.HttpVersion, request.HttpVersionLen)) {
        return request;
    }
    buffer  =   request.HttpVersion + request.HttpVersionLen;

    return request;
}
